/*
 *
 *  Copyright 2015 the original author or authors.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *
 */

package com.flow.framework.api.doc.manager;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.jackson.databind.JavaType;
import com.flow.framework.api.doc.util.ImmutableUtil;
import com.flow.framework.common.util.verify.VerifyUtil;
import com.flow.framework.core.validation.annotation.ImmutableCustomization;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import org.springframework.plugin.core.PluginRegistry;
import springfox.documentation.builders.ModelPropertyBuilder;
import springfox.documentation.schema.*;
import springfox.documentation.schema.plugins.SchemaPluginsManager;
import springfox.documentation.service.AllowableValues;
import springfox.documentation.service.VendorExtension;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.schema.ModelBuilderPlugin;
import springfox.documentation.spi.schema.ModelPropertyBuilderPlugin;
import springfox.documentation.spi.schema.SyntheticModelProviderPlugin;
import springfox.documentation.spi.schema.contexts.ModelContext;
import springfox.documentation.spi.schema.contexts.ModelPropertyContext;
import springfox.documentation.spring.web.DescriptionResolver;

import java.lang.reflect.Field;
import java.util.List;

/**
 * 模型属性解析管理器 参考springfox.documentation.schema.plugins.SchemaPluginsManager和
 * springfox.documentation.swagger.schema.ApiModelPropertyPropertyBuilder
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2023/3/19
 */
public class CustomizationSchemaPluginsManager extends SchemaPluginsManager {

    private final DescriptionResolver descriptions;

    public CustomizationSchemaPluginsManager(
            PluginRegistry<ModelPropertyBuilderPlugin, DocumentationType> propertyEnrichers,
            PluginRegistry<ModelBuilderPlugin, DocumentationType> modelEnrichers,
            PluginRegistry<SyntheticModelProviderPlugin, ModelContext> syntheticModelProviders,
            DescriptionResolver descriptions) {
        super(propertyEnrichers, modelEnrichers, syntheticModelProviders);
        this.descriptions = descriptions;
    }

    @Override
    @SuppressWarnings("deprecation")
    public ModelProperty property(ModelPropertyContext context) {
        ModelProperty modelProperty = super.property(context);
        ModelPropertyBuilder builder = context.getBuilder();
        String description = modelProperty.getDescription();
        Object example = modelProperty.getExample();
        JavaType primaryType = context.getBeanPropertyDefinition().get().getPrimaryType();
        String additionalDescription = ImmutableUtil.getAdditionalDescription(primaryType.getRawClass());
        if (!VerifyUtil.isEmpty(additionalDescription)) {
            example = ImmutableUtil.getExample(primaryType.getRawClass());
        } else {
            Optional<ImmutableCustomization> immutableCustomizationOptional = Annotations.findPropertyAnnotation(context.getBeanPropertyDefinition().get(),
                    ImmutableCustomization.class);
            additionalDescription = ImmutableUtil.getAdditionalDescription(immutableCustomizationOptional);
            if (!VerifyUtil.isEmpty(additionalDescription)) {
                example = ImmutableUtil.getExample(immutableCustomizationOptional);
            }
        }
        if (!VerifyUtil.isEmpty(additionalDescription)) {
            try {
                Field allowableValues = builder.getClass().getDeclaredField("allowableValues");
                allowableValues.setAccessible(true);
                allowableValues.set(builder, null);
                allowableValues.setAccessible(false);
            } catch (Exception ignore) {
            }
        }
        String finalDescription = description + additionalDescription;
        descriptions.resolve(finalDescription);
        builder.description(finalDescription);
        builder.example(example);
        return new CustomizationModelProperty(builder.build());
    }

    @Override
    public List<ModelProperty> syntheticProperties(ModelContext context) {
        return super.syntheticProperties(context);
    }

    public static class CustomizationModelProperty extends ModelProperty {

        public CustomizationModelProperty(ModelProperty modelProperty) {
            this(modelProperty.getName(), modelProperty.getType(), modelProperty.getQualifiedType(), modelProperty.getPosition(),
                    modelProperty.isRequired(), modelProperty.isHidden(), modelProperty.isReadOnly(), modelProperty.isAllowEmptyValue(),
                    modelProperty.getDescription(), modelProperty.getAllowableValues(), modelProperty.getExample(), modelProperty.getPattern(),
                    modelProperty.getDefaultValue(), modelProperty.getXml(), modelProperty.getVendorExtensions());
        }

        public CustomizationModelProperty(String name, ResolvedType type, String qualifiedType, int position, boolean required,
                                          boolean isHidden, boolean readOnly, Boolean allowEmptyValue, String description,
                                          AllowableValues allowableValues, Object example, String pattern, String defaultValue,
                                          Xml xml, List<VendorExtension> vendorExtensions) {
            super(name, type, qualifiedType, position, required, isHidden, readOnly, allowEmptyValue, description, allowableValues,
                    example, pattern, defaultValue, xml, vendorExtensions);
        }

        @Override
        public ModelProperty updateModelRef(Function<ResolvedType, ? extends ModelReference> modelRefFactory) {
            ResolvedType type = getType();
            if (type.getErasedType().isEnum()) {
                ModelRef modelRef = new ModelRef("string", null, null, false);
                try {
                    Field allowableValues = ModelProperty.class.getDeclaredField("modelRef");
                    allowableValues.setAccessible(true);
                    allowableValues.set(this, modelRef);
                    allowableValues.setAccessible(false);
                } catch (Exception ignore) {
                }
                return this;
            }
            return super.updateModelRef(modelRefFactory);
        }
    }
}
